import os
import re
import asyncio
import yt_dlp
from dataclasses import dataclass
from typing import List, Dict, Any, Callable, Optional, Tuple

@dataclass
class VideoInfo:
    """视频信息类"""
    id: str
    title: str
    cover_url: str
    formats: List[Dict[str, Any]]

class BaseDownloader:
    """下载器基类"""
    def __init__(self):
        self.cookies_file = None
    
    def set_cookies_file(self, cookies_file):
        """设置 cookies 文件"""
        self.cookies_file = cookies_file
    
    async def extract_video_info(self, url: str) -> Optional[VideoInfo]:
        """提取视频信息"""
        raise NotImplementedError("子类必须实现此方法")
    
    async def download_video(self, url: str, format_id: str, progress_callback: Callable[[float], None]) -> Tuple[bool, str]:
        """下载视频"""
        raise NotImplementedError("子类必须实现此方法")

class YtdlpDownloader(BaseDownloader):
    """基于yt-dlp的下载器"""
    def __init__(self):
        super().__init__()
        self.download_dir = os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader")
        os.makedirs(self.download_dir, exist_ok=True)
    
    async def extract_video_info(self, url: str) -> Optional[VideoInfo]:
        """提取视频信息"""
        try:
            print(f"YtdlpDownloader.extract_video_info: 开始解析 {url}")
            
            ydl_opts = {
                'quiet': False,  # 设置为False以查看更多输出
                'no_warnings': False,  # 设置为False以查看警告
                'no_color': True,
                'verbose': True,  # 添加详细输出
            }
            
            # 如果有 cookies 文件，添加到选项中
            if self.cookies_file and os.path.exists(self.cookies_file):
                print(f"使用cookies文件: {self.cookies_file}")
                ydl_opts['cookiefile'] = self.cookies_file
            else:
                print("未使用cookies文件")
            
            print(f"yt-dlp选项: {ydl_opts}")
            
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                print("开始调用yt-dlp提取信息...")
                info = ydl.extract_info(url, download=False)
                
                if not info:
                    print("yt-dlp返回空信息")
                    return None
                
                print(f"yt-dlp返回信息: id={info.get('id', '无ID')}, title={info.get('title', '无标题')}")
                print(f"格式数量: {len(info.get('formats', []))}")
                
                # 创建VideoInfo对象
                video_info = VideoInfo(
                    id=info.get('id', ''),
                    title=info.get('title', '未知标题'),
                    cover_url=info.get('thumbnail', ''),
                    formats=info.get('formats', [])
                )
                
                return video_info
        except yt_dlp.utils.DownloadError as e:
            print(f"yt-dlp下载错误: {str(e)}")
            # 检查是否是cookies相关错误
            if "cookies" in str(e).lower():
                print("可能是cookies问题，请检查cookies设置")
            return None
        except Exception as e:
            print(f"yt-dlp未知错误: {type(e).__name__}: {str(e)}")
            import traceback
            traceback.print_exc()
            return None
    
    async def download_video(self, url: str, format_id: str, progress_callback: Callable[[float], bool]) -> Tuple[bool, str]:
        """下载视频"""
        try:
            # 创建进度钩子
            def progress_hook(d):
                if d['status'] == 'downloading':
                    # 计算下载进度
                    if 'total_bytes' in d and d['total_bytes'] > 0:
                        progress = d['downloaded_bytes'] / d['total_bytes']
                    elif 'total_bytes_estimate' in d and d['total_bytes_estimate'] > 0:
                        progress = d['downloaded_bytes'] / d['total_bytes_estimate']
                    else:
                        progress = 0
                    
                    # 调用进度回调
                    continue_download = progress_callback(progress)
                    if not continue_download:
                        raise Exception("下载已取消")
            
            # 设置下载选项
            ydl_opts = {
                'format': format_id,
                'outtmpl': os.path.join(self.download_dir, '%(title)s.%(ext)s'),
                'progress_hooks': [progress_hook],
                'quiet': True,
                'no_warnings': True,
                'no_color': True,
            }
            
            # 如果有 cookies 文件，添加到选项中
            if self.cookies_file and os.path.exists(self.cookies_file):
                ydl_opts['cookiefile'] = self.cookies_file
            
            # 下载视频
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                info = ydl.extract_info(url, download=True)
                
                if not info:
                    return False, "下载失败，无法获取视频信息"
                
                # 获取下载文件路径
                if 'requested_downloads' in info and info['requested_downloads']:
                    filepath = info['requested_downloads'][0]['filepath']
                    return True, filepath
                
                return True, os.path.join(self.download_dir, f"{info.get('title', '视频')}.{info.get('ext', 'mp4')}")
        
        except Exception as e:
            return False, str(e)

class DouyinDownloader(YtdlpDownloader):
    """抖音下载器"""
    def __init__(self):
        super().__init__()
    
    async def extract_video_info(self, url: str) -> Optional[VideoInfo]:
        """提取抖音视频信息"""
        print(f"DouyinDownloader.extract_video_info: 开始解析抖音视频 {url}")
        
        # 处理抖音分享链接，提取真实URL
        if "v.douyin.com" in url:
            print("检测到抖音短链接，尝试获取真实URL")
            try:
                import requests
                response = requests.head(url, allow_redirects=True)
                url = response.url
                print(f"抖音短链接重定向到: {url}")
            except Exception as e:
                print(f"解析抖音短链接失败: {str(e)}")
        
        # 检查cookies文件
        if self.cookies_file:
            print(f"使用cookies文件: {self.cookies_file}")
            if not os.path.exists(self.cookies_file):
                print(f"警告: cookies文件不存在: {self.cookies_file}")
            else:
                print(f"cookies文件大小: {os.path.getsize(self.cookies_file)} 字节")
        else:
            print("警告: 未设置cookies文件")
        
        try:
            # 调用父类方法解析视频
            video_info = await super().extract_video_info(url)
            if video_info:
                print(f"成功解析抖音视频: {video_info.title}, 格式数量: {len(video_info.formats)}")
            else:
                print("抖音视频解析失败: 返回None")
            return video_info
        except Exception as e:
            print(f"抖音视频解析异常: {str(e)}")
            raise

class YoutubeDownloader(YtdlpDownloader):
    """YouTube下载器"""
    def __init__(self):
        super().__init__()

class KuaishouDownloader(YtdlpDownloader):
    """快手下载器"""
    def __init__(self):
        super().__init__()

class XiaohongshuDownloader(YtdlpDownloader):
    """小红书下载器"""
    def __init__(self):
        super().__init__()

class XiguaDownloader(YtdlpDownloader):
    """西瓜视频下载器"""
    def __init__(self):
        super().__init__()

class BilibiliDownloader(YtdlpDownloader):
    """B站下载器"""
    def __init__(self):
        super().__init__()

class ToutiaoDownloader(YtdlpDownloader):
    """今日头条下载器"""
    def __init__(self):
        super().__init__()

class DownloaderFactory:
    """下载器工厂类"""
    def __init__(self):
        self.url_patterns = {
            r'(douyin\.com|iesdouyin\.com)': DouyinDownloader,
            r'(youtube\.com|youtu\.be)': YoutubeDownloader,
            r'kuaishou\.com': KuaishouDownloader,
            r'xiaohongshu\.com': XiaohongshuDownloader,
            r'ixigua\.com': XiguaDownloader,
            r'(bilibili\.com|b23\.tv)': BilibiliDownloader,
            r'toutiao\.com': ToutiaoDownloader,
        }
    
    def get_downloader(self, url: str) -> Optional[BaseDownloader]:
        """根据URL获取合适的下载器"""
        for pattern, downloader_class in self.url_patterns.items():
            if re.search(pattern, url):
                return downloader_class()
        
        return None 